package com.sinochem.yunlian.ship.zuul.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Charsets;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.sinochem.yunlian.ship.api.vo.bs.BsVersionVo;
import com.sinochem.yunlian.ship.api.vo.uc.UcUserInfo;
import com.sinochem.yunlian.ship.api.vo.user.UserInfoSSO;
import com.sinochem.yunlian.ship.common.constant.*;
import com.sinochem.yunlian.ship.common.enums.ResponseCode;
import com.sinochem.yunlian.ship.common.msg.RestResponse;
import com.sinochem.yunlian.ship.common.msg.UCResponse;
import com.sinochem.yunlian.ship.common.service.HttpClientService;
import com.sinochem.yunlian.ship.common.util.CookieHelper;
import com.sinochem.yunlian.ship.common.util.VersionUtil;
import com.sinochem.yunlian.ship.zuul.feign.IUcextUserService;
import com.sinochem.yunlian.ship.zuul.feign.IVersionService;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.net.URLDecoder;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * ${DESCRIPTION}
 *
 * @author cyp
 * @create 2018-01-23 08:25
 */
@Component
public class AdminAccessFilter extends ZuulFilter {

	private Logger LOG = LoggerFactory.getLogger(getClass());

	@Autowired
	private IUcextUserService ucextUserService;

	@Autowired
	private IVersionService versionService;

	@Value("${gate.ignore.startWithAnon}")
	private String startWithAnon;
	
	@Value("${gate.ignore.startWithAuth}")
	private String startWithAuth;

	@Value("${gate.ignore.startWithVer}")
	private String startWithVer;

	@Value("${zuul.prefix}")
	private String zuulPrefix;

	@Value("${sso.apiUrl}")
	private String ssoApiUrl;

	@Value("${sso.timeout}")
	private int ssoTimeout;

	// uc api url
	@Value("${uc.apiUrl}")
	private String ucApiUrl;

	@Value("${uc.appCode}")
	private String appCode;

	@Value("${uc.appKey}")
	private String appKey;

	@Value("${uc.appKeyYY}")
	private String appKeyYY;

	@Autowired
	private HttpClientService httpClientService;
	
	@Autowired
	private RedisTemplate redisTemplate;

	@Override
	public String filterType() {
		return "pre";
	}

	@Override
	public int filterOrder() {
		return 1;
	}

	@Override
	public boolean shouldFilter() {
		return true;
	}

	@Override
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();
		HttpServletRequest request = ctx.getRequest();
		final String requestUri = request.getRequestURI().substring(zuulPrefix.length());
		
		LOG.info("frontend client request zuul admin filter uri:{}" ,requestUri);

		// version版本接口不做任何判断
		if (isStartWithVer(requestUri)) {
			return null;
		}

		try {
			if (appVersionInfo(request,ctx)) {
				setFailedRequest(
						JSON.toJSONString(new RestResponse(ResponseCode.APP_VERSION_UPDATE.getCode(), "App版本强制更新!")),
						HttpStatus.SC_OK);
			}
		} catch (Exception e) {
			LOG.error("用户中心版本更新判断异常,异常栈：{}", e);
			setFailedRequest(
					JSON.toJSONString(new RestResponse(ResponseCode.INVALID_TOKEN.getCode(), "SSO校验用户失败,请重新登录!")),
					HttpStatus.SC_OK);
			return null;
		}

		// 不进行拦截的地址
		if (isStartWithAnon(requestUri)) {
			return null;
		}
		
		String token  = this.getSSOToken(request);
		
		//未登录、登录的地址
		if(isStartWithAuth(requestUri) && Strings.isNullOrEmpty(token)){
			return null;
		}

		// 获取当前登录用户
		UserInfoSSO userInfoSSO = null;
		try {
			userInfoSSO = getUcUser(ctx, token);
		} catch (Exception e) {
			LOG.error("用户中心获取当前用户异常,异常栈：{}", e);
			setFailedRequest(
					JSON.toJSONString(new RestResponse(ResponseCode.INVALID_TOKEN.getCode(), "SSO校验用户失败,请重新登录!")),
					HttpStatus.SC_OK);
			return null;
		}


		// 从用户中心获取用户信息
		UcUserInfo ucextUserInfo = this.getUserInfoFromUcenterExt(userInfoSSO.getUserId());

		// 申请客户端密钥头
		ctx.addZuulRequestHeader(CommonConstants.SSO_TOKEN, token + "");
		if(ucextUserInfo != null){
			ctx.addZuulRequestHeader(CommonConstants.CUR_USER_ID, ucextUserInfo.getId() + "");
			//判断用户是否被禁用
			clearUserInfoSSO(token, ucextUserInfo.getId());
		}
		ctx.addZuulRequestHeader(CommonConstants.CUR_SSO_USER_ID, userInfoSSO.getUserId());
		ctx.addZuulRequestHeader(CommonConstants.CUR_LOGIN_NAME, userInfoSSO.getLoginName());

		return null;
	}

	/**
	 *
	 * 匹配前台资源权限
	 * @param requestUri
	 * @param roleResList
	 * @return
	 */
	private Collection<String> validateUserPermissions(final String requestUri,
			List<String> roleResList) {
		return Collections2.filter(roleResList, new Predicate<String>() {
			@Override
			public boolean apply(String roleResUrl) {
				String uri = roleResUrl.replaceAll("\\{\\*\\}", "[a-zA-Z\\\\d]+");
				String regEx = "^" + uri + "$";
				return (Pattern.compile(regEx).matcher(requestUri).find() || requestUri.startsWith(roleResUrl + "/"));
			}
		});
	}

	/**
	 * 
	 * 从Redis或SSO服务器获取当前登录用户的信息
	 * 
	 * @param token
	 * @param ctx
	 * @return
	 */
	private UserInfoSSO getUcUser(RequestContext ctx, String token) throws Exception {
		if (StringUtils.isBlank(token)) {
			LOG.info("用户中心获取token为空，请核实!");
			throw new Exception("token不能为空!");
		}
		// 调用SSO服务器地址URL
		UserInfoSSO userInfoSSO = this.getUserInfoSSO(token);
		if(userInfoSSO == null){
			LOG.info("用户中心获取用户登录信息为空，用户token过期或不存在，请核实!");
			throw new Exception("用户中心获取用户登录信息为空，用户token过期或不存在，请核实!");
		}
		ctx.addZuulRequestHeader(CommonConstants.SSO_TOKEN, token);
		return userInfoSSO;
	}
		
	/**
	 * 
	 * 从请求中获取token
	 * @param request
	 * @return
	 */
	private String getSSOToken(HttpServletRequest request) {
		String token = "";
		// 从cookie中获取token
		Cookie tokenCookie = CookieHelper.getCookieByName(request, CommonConstants.SSO_TOKEN);
		if (tokenCookie != null) {
			try {
			    token = URLDecoder.decode(tokenCookie.getValue(), Charsets.UTF_8.name());
			} catch(Exception e){
				LOG.info("用户中心从前台cookies中获取token失败，请核实!");
			}
		}
		if (StringUtils.isBlank(token)) {
			token = request.getHeader(CommonConstants.SSO_TOKEN);
		}
		if (StringUtils.isBlank(token)) {
			token = request.getParameter(CommonConstants.SSO_TOKEN);
		}
		return token;
	}

	/**
	 *  URI是否以什么开头,匿名访问
	 * 
	 * @param requestUri
	 * @return
	 */
	private boolean isStartWithAnon(String requestUri) {
		boolean flag = false;
		for (String s : startWithAnon.split(",")) {
			if (requestUri.contains(s)) {
				return true;
			}
		}
		return flag;
	}

	/**
	 *  URI是否以什么开头,匿名访问,版本号
	 *
	 * @param requestUri
	 * @return
	 */
	private boolean isStartWithVer(String requestUri) {
		boolean flag = false;
		for (String s : startWithVer.split(",")) {
			if (requestUri.contains(s)) {
				return true;
			}
		}
		return flag;
	}
	
	/**
	 *  URI是否以什么开头,登录、未登录访问
	 * 
	 * @param requestUri
	 * @return
	 */
	private boolean isStartWithAuth(String requestUri) {
		boolean flag = false;
		for (String s : startWithAuth.split(",")) {
			if (requestUri.contains(s)) {
				return true;
			}
		}
		return flag;
	}

	/**
	 * Reports an error message given a response body and code.
	 *
	 * @param body
	 * @param code
	 */
	private void setFailedRequest(String body, int code) {
		LOG.debug("Reporting error ({}): {}", code, body);
		RequestContext ctx = RequestContext.getCurrentContext();
		ctx.setResponseStatusCode(code);
		ctx.getResponse().setContentType("text/json;charset=UTF-8");
		if (ctx.getResponseBody() == null) {
			ctx.setResponseBody(body);
			ctx.setSendZuulResponse(false);
		}
	}

	/**
	 *
	 * @author: chengyiping
	 * @description:  从本地用户中心查询用户信息
	 * @date: Created in 2018/3/30 19:52
	 * @modified By :
	 * @version:
	 */
	public UcUserInfo getUserInfoFromUcenterExt(String userId){
		String userBasicKey = String.format(RedisKey.UCENTEREXT_USER_BASIC_USERID_PREFIX, userId);
		Object redisUserInfo = redisTemplate.opsForValue().get(userBasicKey);
		//判断Redis是否存在，不存在则从用户中心取数据
		if (redisUserInfo == null) {
			UcUserInfo ucextUserInfo = ucextUserService.findByUserId(Long.parseLong(userId));;
			if(ucextUserInfo != null){
				// 设置用户缓存
				redisTemplate.opsForValue().set(userBasicKey, ucextUserInfo, ssoTimeout, TimeUnit.SECONDS);
				return ucextUserInfo;
			} else {
				LOG.info("用户中心接口返回数据为空,请核查!");
				return null;
			}
		} else {
			UcUserInfo ucextUserInfo = (UcUserInfo)redisUserInfo;
			return ucextUserInfo;
		}
	}

	/**
	 *
	 * @author: chengyiping
	 * @description:  判断用户登录状态
	 * @date: Created in 2018/3/30 19:52
	 * @modified By :
	 * @version:
	 */
	public UserInfoSSO getUserInfoSSO(String token){
		String loginTokenKey = String.format(RedisKey.UCENTER_LOGIN_TOKEN_PREFIX, token);
		Object redisUserInfo = redisTemplate.opsForValue().get(loginTokenKey);
		//判断Redis是否存在，不存在则从SSO取数据
		if (redisUserInfo == null) {
			Map<String,String> requestParamMap = new HashMap<String,String>();
			Map<String,String> hearderParamMap = new HashMap<String,String>();
			hearderParamMap.put("token",token);
			hearderParamMap.put("appCode",appCode);
			UCResponse ucResponse = httpClientService.requestUCAPI(ssoApiUrl, token, UCHttpRequest.UC_HTTP_POST, UCAPIConstant.UC_USER_SSO_USERMESSAGE, requestParamMap, hearderParamMap);
			if(ucResponse != null && ucResponse.getStatus() != null && UCHttpRequest.UC_HTTP_SUCCESS.intValue() == ucResponse.getStatus().intValue() && ucResponse.getData() != null){
				JSONObject userDataJson = (JSONObject) ucResponse.getData();
				UserInfoSSO userInfoSSO = (UserInfoSSO) JSONObject
						.toJavaObject(userDataJson.getJSONObject("user"), UserInfoSSO.class);
				if (!StringUtils.isBlank(userInfoSSO.getUserId()) && !StringUtils.isBlank(userInfoSSO.getLoginName())) {
					// 登录信息
					redisTemplate.opsForValue().set(loginTokenKey, userInfoSSO, ssoTimeout, TimeUnit.SECONDS);
					// 在线信息
					String onlineKey = String.format(RedisKey.UCENTER_ONLINE_PREFIX, userInfoSSO.getLoginName());
					redisTemplate.opsForValue().set(onlineKey, token, ssoTimeout, TimeUnit.SECONDS);
					return userInfoSSO;
				} else {
					LOG.info("用户中心接口返回权限数据为空,接口url:{},接口token:{},返回内容:{},请核查!", ucApiUrl + UCAPIConstant.UC_USER_SSO_USERMESSAGE, token, ucResponse);
					return null;
				}
			} else {
				LOG.info("用户中心接口返回权限数据为空,接口url:{},接口token:{},返回内容:{},请核查!", ucApiUrl + UCAPIConstant.UC_USER_SSO_USERMESSAGE, token, ucResponse);
				return null;
			}
		} else {
			UserInfoSSO userInfoSSO = (UserInfoSSO)redisUserInfo;
			return userInfoSSO;
		}
	}

	/**
	 *
	 * @author: chengyiping
	 * @description:  判断用户登录状态
	 * @date: Created in 2018/3/30 19:52
	 * @modified By :
	 * @version:
	 */
	public void clearUserInfoSSO(String token, Long id){
		String loginTokenKey = String.format(RedisKey.UCENTER_LOGIN_TOKEN_PREFIX, token);
		String userStatusKey = String.format(RedisKey.UCENTEREXT_USER_STATUS_ID_PREFIX, id);
		//判断Redis是否存在，存在清空缓存
		if(redisTemplate.hasKey(userStatusKey)){
			Object statusObject = redisTemplate.opsForValue().get(userStatusKey);
			if(statusObject != null){
				Integer status = (Integer) statusObject;
				if(status == DisableStatus.STATUS_DISABLE){
					if(redisTemplate.hasKey(loginTokenKey)){
						redisTemplate.delete(loginTokenKey);
					}
				}
			}
		}
	}

	/**
	 * @author: chengyiping
	 * @description:  获取用户角色
	 * @date: Created in 2018/3/30 19:52
	 * @modified By :
	 * @version:
	 */
	public List<String> getUserRoleInfo(String userId,String token) {
		String userRoleKey = String.format(RedisKey.UCENTEREXT_ACL_USERROLE_PREFIX, userId);
		Object redisUserRoleInfo = redisTemplate.opsForValue().get(userRoleKey);
		//判断Redis是否存在，不存在则从UC中获取
		if (redisUserRoleInfo == null) {
			Map<String,String> requestParamMap = new HashMap<String,String>();
			Map<String,String> hearderParamMap = new HashMap<String,String>();
			hearderParamMap.put("token",token);
			hearderParamMap.put("appCode",appCode);
			UCResponse ucResponse = httpClientService.requestUCAPI(ssoApiUrl, token, UCHttpRequest.UC_HTTP_GET, UCAPIConstant.UC_USER_ACL_USERROLE, requestParamMap, hearderParamMap);
			if(ucResponse != null && ucResponse.getStatus() != null && UCHttpRequest.UC_HTTP_SUCCESS.intValue() == ucResponse.getStatus().intValue() && ucResponse.getData() != null){
				List<String> roleCodeList = new ArrayList<String>();
				JSONArray roleDataJson = (JSONArray) ucResponse.getData();
				if (roleDataJson != null && !roleDataJson.isEmpty()) {
					for (int i = 0; i < roleDataJson.size(); i++) {
						String roleCode = (String)roleDataJson.get(i);
						roleCodeList.add(roleCode);
					}
				} else {
					LOG.info("用户中心接口返回权限数据为空,接口url:{},接口token:{},返回内容:{},请核查!", ucApiUrl + UCAPIConstant.UC_USER_ACL_USERROLE, token, ucResponse);
				}
				// 角色code列表缓存
				if(roleCodeList != null && !roleCodeList.isEmpty()) {
					redisTemplate.opsForValue().set(userRoleKey, roleCodeList, ssoTimeout, TimeUnit.SECONDS);
				}
				return roleCodeList;
			} else {
				LOG.info("用户中心接口返回权限数据为空,接口url:{},接口token:{},返回内容:{},请核查!", ucApiUrl + UCAPIConstant.UC_USER_ACL_USERROLE, token, ucResponse);
				return null;
			}
		} else {
			List<String> roleCodeList = (List<String>)redisUserRoleInfo;
			return roleCodeList;
		}
	}

	/**
	 * @author: chengyiping
	 * @description:  获取角色权限
	 * @date: Created in 2018/3/30 19:52
	 * @modified By :
	 * @version:
	 */
	public List<String> getRoleResInfo(String roleCode, String token) {
		String roleResKey = String.format(RedisKey.UCENTEREXT_ACL_ROLERES_PREFIX, roleCode);
		Object redisRoleResInfo = redisTemplate.opsForValue().get(roleResKey);
		//判断Redis是否存在，不存在则从UC中获取
		if (redisRoleResInfo == null) {
			Map<String,String> requestParamMap = new HashMap<String,String>();
			Map<String,String> hearderParamMap = new HashMap<String,String>();
			hearderParamMap.put("token",token);
			hearderParamMap.put("appCode",appCode);
			requestParamMap.put("roleCodes",roleCode);
			UCResponse ucResponse = httpClientService.requestUCAPI(ssoApiUrl, token, UCHttpRequest.UC_HTTP_GET, UCAPIConstant.UC_USER_ACL_ROLERES, requestParamMap, hearderParamMap);
			if(ucResponse != null && ucResponse.getStatus() != null && UCHttpRequest.UC_HTTP_SUCCESS.intValue() == ucResponse.getStatus().intValue() && ucResponse.getData() != null){
				List<String> urlList = new ArrayList<String>();
				JSONObject dataJSONObject = (JSONObject) ucResponse.getData();
				JSONObject roleJSONObject = dataJSONObject.getJSONObject(roleCode);
				// 前台AppKey
				JSONObject roleAppKeyJSONObject = roleJSONObject.getJSONObject(appKey);
				if(roleAppKeyJSONObject != null) {
					getRoleResUrlList(urlList, roleAppKeyJSONObject);
				}
				// 运营AppKey
				JSONObject roleAppKeyYYJSONObject = roleJSONObject.getJSONObject(appKeyYY);
				if(roleAppKeyYYJSONObject != null) {
					getRoleResUrlList(urlList, roleAppKeyYYJSONObject);
				}
				if(urlList != null && !urlList.isEmpty()){
					// 设置缓存
					redisTemplate.opsForValue().set(roleResKey, urlList, ssoTimeout, TimeUnit.SECONDS);
					return urlList;
				} else {
					LOG.info("用户中心接口返回权限数据为空,接口url:{},接口token:{},返回内容:{},请核查!", ucApiUrl + UCAPIConstant.UC_USER_ACL_ROLERES, token, ucResponse);
					return null;
				}
			} else {
				LOG.info("用户中心接口返回权限数据为空,接口url:{},接口token:{},返回内容:{},请核查!", ucApiUrl + UCAPIConstant.UC_USER_ACL_ROLERES, token, ucResponse);
				return null;
			}
		} else {
			List<String> urlList = (List<String>)redisRoleResInfo;
			return urlList;
		}
	}

	/**
	 *
	 * 判断App端版本升级
	 * @param request
	 * @return
	 * @throws Exception
	 */
	private boolean appVersionInfo(HttpServletRequest request,RequestContext ctx) throws Exception {
		//如果clientVersion有误，直接允许访问接口
		String appId = request.getHeader("appId");
		String clientVersion = request.getHeader("version");
		if(Strings.isNullOrEmpty(appId)){
              return false;
		} else {
			String serverVersion = getServerVersion(request);
			ctx.getResponse().setHeader(CommonConstants.APP_SERVER_VERSION, serverVersion);
			if(Strings.isNullOrEmpty(clientVersion) ||
					!VersionUtil.validateClientVersion(clientVersion)){
				return false;
			}
			if(VersionUtil.validateVersionUpdate(clientVersion,serverVersion)){
				return true;
			}
		}
        return false;
	}

	/**
	 * 获取服务端版本
	 * @param request
	 * @return
	 */
	public String getServerVersion(HttpServletRequest request) throws Exception {
		String appId = request.getHeader("appId");
		String appIdKey = String.format(RedisKey.KEY_VERSION_PREFIX , appId);
		Object redisVersionInfo = redisTemplate.opsForValue().get(appIdKey);
		//判断Redis是否存在
		if (redisVersionInfo == null) {
			BsVersionVo bsVersionVo = versionService.getVersionInfoByAppId(appId);
			//设置失效时间
			redisTemplate.opsForValue().set(appIdKey, bsVersionVo, ssoTimeout, TimeUnit.SECONDS);
			return bsVersionVo.getClientVersion();
		} else {
			BsVersionVo bsVersionVo = (BsVersionVo)redisVersionInfo;
			return bsVersionVo.getClientVersion();
		}
	}

	/**
	 *
	    * @author: chengyiping
	    * @description: 按照AppKey分类获取资源列表
	    * @date: Created in 2018/4/23 14:51
	    * @modified By :
	    * @version:
	*/
	private void getRoleResUrlList(List<String> urlList, JSONObject roleAppKeyJSONObject){
		JSONArray menusJSONArray = roleAppKeyJSONObject.getJSONArray("menus");
		JSONArray operationsJSONArray = roleAppKeyJSONObject.getJSONArray("operations");
		if (menusJSONArray != null && !menusJSONArray.isEmpty()) {
			for (int i = 0; i < menusJSONArray.size(); i++) {
				JSONObject menuJSONObject = menusJSONArray.getJSONObject(i);
				urlList.add(menuJSONObject.getString("url"));
			}
		}
		if (operationsJSONArray != null && !operationsJSONArray.isEmpty()) {
			for (int i = 0; i < operationsJSONArray.size(); i++) {
				JSONObject operationJSONObject = operationsJSONArray.getJSONObject(i);
				urlList.add(operationJSONObject.getString("url"));
			}
		}
	}

}